﻿using System;
using System.ComponentModel;
using System.Linq;
using System.Windows;
using System.Windows.Media;

namespace GameLogic.Logic

{
	public class MovingCollidableBehavior : GameLoopBehavior<FrameworkElement>, ICollidable
	{
        #region Dependency Properties
        [Category("Movement Properties")]
        public double Speed
        {
            get
            {
                return (double)GetValue(SpeedProperty);
            }
            set
            {
                SetValue(SpeedProperty, value);
            }
        }
        public static readonly DependencyProperty SpeedProperty =
            DependencyProperty.Register("Speed", typeof(double), typeof(MovingCollidableBehavior), new PropertyMetadata(null));

        [Category("Movement Properties")]
        public double Direction
        {
            get
            {
                return (double)GetValue(DirectionProperty);
            }
            set
            {
                SetValue(DirectionProperty, value);
            }
        }
        public static readonly DependencyProperty DirectionProperty =
            DependencyProperty.Register("Direction", typeof(double), typeof(MovingCollidableBehavior), new PropertyMetadata(null));

        [Category("Movement Properties")]
        public bool AutoStart { 
            get 
            { return (bool)GetValue(AutoStartProperty); 
            } 
            set { 
                SetValue(AutoStartProperty, value); 
            } 
        }
        public static readonly DependencyProperty AutoStartProperty = 
            DependencyProperty.Register("AutoStart", typeof(bool), typeof(MovingCollidableBehavior), new PropertyMetadata(null));

        [Category("Collision Properties")]
        public bool CollisionEnabled
        {
            get
            {
                return (bool)GetValue(CollisionEnabledProperty);
            }
            set
            {
                SetValue(CollisionEnabledProperty, value);
            }
        }
        public static readonly DependencyProperty CollisionEnabledProperty =
            DependencyProperty.Register("Collision Enabled", typeof(bool), typeof(MovingCollidableBehavior), new PropertyMetadata(null));

        [Category("Collision Properties")]
        public CollisionTypes CollisionType
        {
            get
            {
                return (CollisionTypes)GetValue(CollisionTypeProperty);
            }
            set
            {
                SetValue(CollisionTypeProperty, value);
            }
        }
        public static readonly DependencyProperty CollisionTypeProperty =
            DependencyProperty.Register("Collision Type", typeof(CollisionTypes), typeof(MovingCollidableBehavior), new PropertyMetadata(null));

        //[Category("Collision Properties")]
        //public double Bounciness
        //{
        //    get { return (double)GetValue(BouncinessProperty); }
        //    set { SetValue(BouncinessProperty, value); }
        //}
        //public static readonly DependencyProperty BouncinessProperty =
        //    DependencyProperty.Register("Bounciness", typeof(double), typeof(MovingCollidableBehavior), new PropertyMetadata(2.0, null));
        
        #endregion

        #region Public Properties
        public Rect Rect
		{
			get
			{
                try
                {
                    UIElement root = this.GameLoop.RootElement as UIElement;
                    Point position = this.AssociatedObject.TransformToVisual(root).Transform(new Point(0, 0));
                    return new Rect(position, new Size(this.AssociatedObject.ActualWidth, this.AssociatedObject.ActualHeight));
                }
                catch
                {
                    Point position = new Point(0, 0);
                    return new Rect(position, new Size(this.AssociatedObject.ActualWidth, this.AssociatedObject.ActualHeight));
                }
			}
		}

        public double X
		{
			get { return this.Rect.X; }
		}

		public double Y
		{
			get { return this.Rect.Y; }
		}

        public Vector2D Velocity
		{
			get;
			set;
		}

        public enum CollisionTypes
        {
            Clamp,   // Resolve collision by stoping movement
            Bounce, // Resolve collision by bouncing off
            None   // Don't resolve collision with default action, but still fire collision event
        }

        #endregion

        #region Private Properties
        private bool isMoving;
        private double vo = 0;
        private double startX, startY;
        private double startDirection;
        private double startSpeed;
        #endregion

        //
        // Methods
        //        
        public void SetPosition(Point p)
		{
			double diffX = p.X - this.X;
			double diffY = p.Y - this.Y;
			MatrixTransform matrixTransform = new MatrixTransform();
			TransformGroup group = new TransformGroup();
			TranslateTransform translateTransform = new TranslateTransform();
            if (this.AssociatedObject.RenderTransform != null)
			{
                group.Children.Add(this.AssociatedObject.RenderTransform);
			}
			translateTransform.X = diffX;
			translateTransform.Y = diffY;
			group.Children.Add(translateTransform);
			matrixTransform.Matrix = group.Value;
            this.AssociatedObject.RenderTransform = matrixTransform;
            this.AssociatedObject.UpdateLayout();
		} 

        private void InitializeVelocity()
        {
            double vx = this.vo * Math.Cos(this.Direction * Math.PI / 180);
            double vy = this.vo * Math.Sin(this.Direction * Math.PI / 180);
            this.Velocity = new Vector2D(vx, vy);
        }

        //
        // Events
        //
        protected override void OnAttached()
        {
            base.OnAttached();
            this.AssociatedObject.Loaded += this.OnAssociatedObjectLoaded;
        }

        protected override void OnGameLoopInitialized()
        {
            base.OnGameLoopInitialized();
            this.GameLoop.Update += this.OnGameLoop_Update;
            this.GameLoop.Reset += this.OnGameLoop_Reset;
            
            // If element has collision enabled
            if (this.CollisionEnabled)
            {
                // Add element to static collision list
                this.GameLoop.RegisterAsStaticCollidable(this.AssociatedObject);
                this.GameLoop.RegisterAsMovingCollidable(this.AssociatedObject);
                // Listen for Collision event
                this.GameLoop.Collision += this.OnGameLoop_Collision;
            }
        }      

        private void OnAssociatedObjectLoaded(object sender, RoutedEventArgs e)
        {
            if (this.AutoStart)
            {
                this.vo = this.Speed;
                this.isMoving = true;
            }
            else
            {
                this.vo = 0;
                this.isMoving = false;
            }

            this.startX = this.X;
            this.startY = this.Y;
            this.InitializeVelocity();
            this.startDirection = this.Direction;
            this.startSpeed = this.Speed;
        }


        protected override void OnDetaching()
        {
            base.OnDetaching();
            this.AssociatedObject.Loaded -= this.OnAssociatedObjectLoaded;
        }

        private void OnGameLoop_Reset(object sender, EventArgs e)
        {
            this.UnregisterCollidableIfNecessary();

            // Reset movement
            this.vo = this.startSpeed;
            this.SetPosition(new Point(this.startX, this.startY));          
            this.Direction = this.startDirection;
            this.InitializeVelocity();  
        }

        private void UnregisterCollidableIfNecessary()
        {
            // check to see if we're still in the tree and remove ourselves if not
            FrameworkElement element = this.AssociatedObject;
            while (element != null)
            {
                if (element.Equals(this.GameLoop.RootElement))
                {
                    return;
                }
                element = VisualTreeHelper.GetParent(element) as FrameworkElement;
            }

            // Remove element from all collision lists
            this.GameLoop.UnregisterAsStaticCollidable(this.AssociatedObject);
            this.GameLoop.UnregisterAsMovingCollidable(this.AssociatedObject);
        }
       
        // Turn movement on
        public void StartMoving()
        {
            this.isMoving = true;
        }

        // Turn movement off
        public void StopMoving()
        {
            this.isMoving = false;
        }

        // Toggle movement
        public void SwichMoving()
        {
            this.isMoving = !this.isMoving;
        }

        private void OnGameLoop_Update(object sender, EventArgs e)
        {
            if (this.isMoving)
            {
                double vx = this.Velocity.X;
                double vy = this.Velocity.Y;

                this.SetPosition(new Point(this.X + vx, this.Y + vy));
            }
        }

        private void OnGameLoop_Collision(object sender, CollisionEventArgs e)
        {
            foreach (CollisionInformation collisionInformation in (from collisionInfo in e.CollisionInformation
                                                                   where object.Equals(this.AssociatedObject, collisionInfo.CollidingElement)
                                                                   select collisionInfo))
            {
                switch (CollisionType)
                {
                    case CollisionTypes.None:
                        break;

                    case CollisionTypes.Bounce:
                        Bounce(collisionInformation.Normal);
                        break;

                    case CollisionTypes.Clamp:
                    default:
                        Clamp(collisionInformation.Normal);
                        break;
                }    
            }
        }

        // Resolve collision by bouncing off
        private void Bounce(Vector2D collisionNormal)
        {
            double vx = this.Velocity.X;
            double vy = this.Velocity.Y;

            // move the object out of the collision region
            Point newPosition = new Point(this.X, this.Y);
            if (collisionNormal.X < 0)
            {
                newPosition.X -= 2 * vx; // old value = 2
            }
            if (collisionNormal.Y < 0)
            {
                newPosition.Y -= 2 * vy; // old value = 2
            }
            this.SetPosition(newPosition);

            // deflect the velocity around the normal of collision
            double dot = vx * collisionNormal.X + vy * collisionNormal.Y;
            vx -= (2 * dot * collisionNormal.X);
            vy -= (2 * dot * collisionNormal.Y);

            this.Velocity = new Vector2D(vx, vy);
            this.Direction = Math.Atan2(vy, vx) * 180 / Math.PI;
        }

        // Resolve collision by stopping movement
        private void Clamp(Vector2D collisionNormal)
        {
            double vx = this.Velocity.X;
            double vy = this.Velocity.Y;

            // move the object out of the collision region
            Point newPosition = new Point(this.X, this.Y);
            if (collisionNormal.X < 0)
            {
                newPosition.X -= 2 * vx;
            }
            if (collisionNormal.Y < 0)
            {
                newPosition.Y -= 2 * vy;
            }
            this.SetPosition(newPosition);

            // stop moving
            this.vo = 0;
            this.Velocity = new Vector2D(0, 0);
        }
    }
}
